package ca;

import java.util.Random;

import evaluator.ServerCommunication;
import evaluator.TimeOverException;

public class CulturalGA {
	private NormativeComponent normativeComponent;
	private SituationalComponent situationalComponent;
	private BeliefSpace beliefSpace;
	
	private double[][] population;
	private double[][] selected;
	private double[][] recombined;
	private double[] fitnessSelected;
	private double[] populationFitness;
	private int populationSize;
	private int nParameters;
	private Random random;
	private ServerCommunication server;
	private double bestFitness;
	private double[] bestIndividual;
	private double sum;
	private double max;
	private double min;
	private int nSituationalIndividuals;

	static private final int simulationTime = 600;
	//static private final double pCrossover = 0.6;
	static private final double mutation = 0.001;
	static private final double situational = 0.4;

	public CulturalGA(ServerCommunication server, int nParameters, int populationSize, int nSituationalIndividuals){
		this.populationSize = populationSize;
		this.nParameters = nParameters;
		this.server = server;
		this.nSituationalIndividuals = nSituationalIndividuals;
		bestFitness = Double.MAX_VALUE;
		bestIndividual = new double[this.nParameters];
		sum = 0;
		max = Double.MAX_VALUE;
		min = Double.MIN_VALUE;
		
		population = new double[populationSize][nParameters];
		populationFitness = new double[populationSize];

		beliefSpace = new BeliefSpace(nSituationalIndividuals, nParameters);
		
		
	}
	
	public void initBeliefSpace(){
		//Seta os valores do domino do problema (limites maximos e minimos) de cada dimensao
		beliefSpace.setDomainDimension(0, 0.1, 0.9);
		
		
		beliefSpace.accept(population, populationFitness);
		beliefSpace.initSituationalComponent();
		beliefSpace.adjustBeliefSpace(population, populationFitness, nSituationalIndividuals);
	}
	
	public void initPopulationalSpace(){
		//Inicializacao aleatoria
				for(int i = 0; i < populationSize; i++){
					for(int j = 0; j < nParameters; j++){
						//Utilizar o componente normativo para usar os limites corretos
						this.population[i][j] = random.nextDouble();
					}
				}
	}
	
	public double[] run(int generation) throws Exception{
		initPopulationalSpace();
		initBeliefSpace();
		for(int i = 0; i < generation; i++){
			
			//Avaliacao fitness
			evaluateFitness();			
			
			System.out.println("Geracao " + i + " min: " + min + " media: " + (float) (sum/this.populationSize) +" max :" + max);

			//Aceita individuos e ajusta o espaco de crenca
			beliefSpace.accept(population, populationFitness);
			beliefSpace.adjustBeliefSpace();
			
			crossover();
			
			mutation();
			
			//Selecionar nova populacao baseado no recombined?
		}
	}

	private void mutation() {
		for(int i = 0; i < populationSize; i++){
			for(int k = 0; k < nParameters; k++){
				if(random.nextDouble() <= mutation){
					//Usando o componente normativo para a mutacao
					if(random.nextDouble() < 0.5){
						recombined[i][k] += random.nextDouble() * (beliefSpace.getNormativeComponent().getMax(k) - recombined[i][k]);
					} else {
						recombined[i][k] += random.nextDouble() * (recombined[i][k] - beliefSpace.getNormativeComponent().getMin(k));
					} 
				}
			}
		}
	}

	private void crossover() {
		//Selecao (Torneio binario)
		binaryTour();
		
		recombined = new double[populationSize][nParameters];
		for(int j = 0; j < populationSize; j++){
			int indexSituational = random.nextInt(nSituationalIndividuals);
			double rand = random.nextDouble();
			double[] bestSelected, worstSelected;
			if(rand <= situational){
				if(beliefSpace.getSituationalComponent().getIndividualFitness()[indexSituational] < fitnessSelected[j]){
					bestSelected = beliefSpace.getSituationalComponent().getIndividuals()[indexSituational];
					worstSelected = selected[j];
				} else {
					worstSelected = beliefSpace.getSituationalComponent().getIndividuals()[indexSituational];
					bestSelected = selected[j];
				}
			} else {
				if(fitnessSelected[j] < fitnessSelected[j+1]){
					bestSelected = selected[j];
					worstSelected = selected[(j+1) % populationSize];
				} else {
					bestSelected = selected[(j+1) % populationSize];
					worstSelected = selected[j];
				}
			}
			
			for(int k = 0; k < nParameters; k++){
				//Wirght[918] - Computation Intelligence - Engelbrecht
				//Talves usar individuos do espaco situacional com alguma probabilidade des escolher os mesmos...
				if(rand <= situational){
					if(beliefSpace.getSituationalComponent().getIndividualFitness()[indexSituational] < fitnessSelected[j])
					recombined[j][k] = random.nextDouble() * (bestSelected[k] - worstSelected[k]) + bestSelected[k];
				} else {
					recombined[j][k] = random.nextDouble() * (bestSelected[k] - worstSelected[k]) + bestSelected[k];					
				}
			}
			
		}
	}

	private void binaryTour() {
		selected = new double[this.populationSize][this.nParameters];
		
		for(int j = 0; j < this.populationSize; j++){
			int a,b;
			
			do{
				a = random.nextInt(this.populationSize);
				b = random.nextInt(this.populationSize);
			} while(a == b);
			
			for(int k = 0; k < this.nParameters; k++){
				if(populationFitness[a] < populationFitness[b]){
					selected[j][k] = population[a][k];
					fitnessSelected[j] = populationFitness[a];
				} else {
					selected[j][k] = population[b][k];
					fitnessSelected[j] = populationFitness[b];
				}
			}
		}
	}

	private void evaluateFitness() throws Exception {
		sum = 0;
		max = Double.MAX_VALUE;
		min = Double.MIN_VALUE;
		
		for(int j = 0; j < this.populationSize; j++){
			Double f = null;
			try{
			f = (Double) server.launchSimulation(this.population[j], simulationTime);
			} catch (TimeOverException e){
				System.exit(-1);
			}
			
			this.populationFitness[j] = f.doubleValue();

			if(this.populationFitness[j] > max){
				max = populationFitness[j];
			}
			
			if(this.populationFitness[j] < min){
				min = this.populationFitness[j];
			}
				
			sum += populationFitness[j];
			
			if(populationFitness[j] < bestFitness){
				bestFitness = this.populationFitness[j];
				for(int k = 0; k < this.nParameters; k++){
					bestIndividual[k] = population[j][k];
				}
			}
		}
		
	}
}
